﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.Framework.Communication.Configuration.Adapters.Stm;
using System.Threading;
using System.ComponentModel;
using FileHelpers;
using System.Xml.Serialization;
using System.IO;
using PSE.Framework.Communication.Configuration.Stm;

namespace PSE.Framework.Communication.Adapters.Stm
{
    public class StmInputAdapter : AsyncInputAdapter<StmInputConfiguration, StmOpenParameter, StmBeginReceiveParameter>
    {
        #region Propriedades de Controle
        private int _waitForConnectTimeOut = 5000;//TODO: - colocar no arquivo de configuracao
        private ManualResetEvent _waitForConnect;
        private ManualResetEvent _receiveEnding;
        #endregion

        #region Cache de objetos utilizados para deserializar as mensagens
        private Dictionary<string, XmlSerializer> _xmlDeserializers;
        private Dictionary<string, RecordInfo> _plainTextDeserializers;
        #endregion

        #region Client STM
        private Interop.STMCom.TSTMComClass _stmClient;
        #endregion

        #region Construtor
        public StmInputAdapter()
        {
        }
        #endregion

        #region Tratamento de Eventos
        void _stmClient_OnStarted()
        {
            ///
        }

        void _stmClient_OnReceiveData(string LastMsgId, string SPF_Header, string DataPtr, int DataSize)
        {
            //se nao estiver finalizando o recebimento, receber a mensagem e repassar
            if (!_receiveEnding.WaitOne(1))
            {
                try
                {
                    ////empacotar a mensagem de forma que todos os dados recebidos estejam em um objeto
                    StmMessage msg = new StmMessage(LastMsgId, DataPtr);

                    ////processar e entregar a mensagem
                    base.ProcessAndDeliveryMessage(msg);
                }
                catch (Exception ex)
                {
                    //caso ocorra algum erro no tratamento da mensagem recebida, disparar o evento de receive error
                    base.OnMessageReceivedError(new MessageReceivedErrorEventArgs(ex));
                }
                finally
                {
                    //indicar para o Stm que o proximo registro pode ser tratado (retirado da documentação do STM)
                    _stmClient.Ack = true;
                }
            }
            else
            {
                _stmClient.Ack = false;
            }
        }

        void _stmClient_OnError(int Error, string Msg, string Desc)
        {
            //desconectar do servidor STM
            _stmClient.Disconnect();

            //so repassar os erros gerados pelo stm caso o client esteja possibilitado de receber mensagens
            //(codificado dessa forma devido ao comportamento estranho do stm, que após chamar o método disconnect, dispara o seguinte erro diversas vezes: "Logical connection not established. Perdeu a conexao MMTP".)
            if (Error == 1 && !this.CanReceive)
            {
                return;
            }


            //criar uma exception com os dados retornados pelo client
            StmException ex = new StmException(Error, Msg, Desc);
            MessageReceivedErrorEventArgs ea = new MessageReceivedErrorEventArgs(ex);

            //disparar evento de erro no recebimento de mensagem
            base.OnMessageReceivedError(ea);
        }

        void _stmClient_OnDisconnect(string desc)
        {
            ///
        }

        void _stmClient_OnConnect()
        {
            //indicar que a conexao foi estabelecida
            _waitForConnect.Set();
        }

        void _stmClient_OnAcceptStart()
        {
            ///
        }
        #endregion

        protected override object Deserialize(object message, string action)
        {
            StmMessage stmMessage = message as StmMessage;

            //de acordo com a action, verificar qual o tipo da mensagem (XML, plainText)
            //se for um mensagem "xml"
            if (_xmlDeserializers.ContainsKey(action))
            {
                StringReader reader = new StringReader(System.Convert.ToString(stmMessage.Body));
                stmMessage.Body = _xmlDeserializers[action].Deserialize(reader);
            }
            //se for uma mensagem "plainText"
            else if (_plainTextDeserializers.ContainsKey(action))
            {
                stmMessage.Body = _plainTextDeserializers[action].StringToRecord(System.Convert.ToString(stmMessage.Body));
            }

            return stmMessage;
        }

        /// <summary>
        /// Incia o processo de recebimento de mensagens
        /// </summary>
        /// <param name="parameter"></param>
        public override bool BeginReceive(StmBeginReceiveParameter parameter)
        {
            base.BeginReceive(parameter);

            _receiveEnding.Reset();
            _waitForConnect.Reset();

            if (parameter != null && !string.IsNullOrEmpty(parameter.Filter))
                _stmClient.Filter = parameter.Filter;
            else if (!string.IsNullOrEmpty(this.Configuration.Filter))
                _stmClient.Filter = this.Configuration.Filter;
            else
                _stmClient.Filter = string.Empty;

            _stmClient.Ack = true;
            _stmClient.Compressed = true;

            bool conectou = false;
            //tenta conectar (assincrono)

            if (_stmClient.Connect())
            {
                if (_waitForConnect.WaitOne(_waitForConnectTimeOut))//aguarda um determinado tempo, ate que a conexao seja estabelecida
                {
                    conectou = true;
                }

                if (conectou)
                {
                    //iniciar o recebimento de mensagens
                    if (parameter != null)
                        _stmClient.StartRequest(parameter.MessageId);
                    else
                        _stmClient.StartRequest("");
                }
                else
                {
                    _stmClient.Disconnect();
                    ///TODO: disparar erro com mais detalhes, verificar se o evento de erro do tgp sera disparado nesse caso
                    throw new AdapterException("Was not possible to connect on STM server", this.Configuration.Name, AdapterDirection.Input);
                }
            }
            else
            {
                throw new AdapterException("Was not possible to connect on STM server", this.Configuration.Name, AdapterDirection.Input);
            }

            return false;
        }

        /// <summary>
        /// Finaliza o recebimento de mensagens
        /// </summary>
        public override void EndReceive()
        {
            _receiveEnding.Set();
            base.EndReceive();

            //fechar a conexao com o STM
            _stmClient.Disconnect();
        }

        /// <summary>
        /// Abre o provider de recebimento de mensagens STM
        /// </summary>
        /// <param name="config"></param>
        /// <param name="parameter"></param>
        public override void Open(StmInputConfiguration config, StmOpenParameter parameter)
        {
            if (parameter == null)
                throw new ArgumentNullException("parameter");

            if (parameter.Password == null)
                throw new ArgumentNullException("parameter.Password");

            base.Open(config, parameter);

            _waitForConnect = new ManualResetEvent(false);
            _receiveEnding = new ManualResetEvent(false);

            //instanciar o client STM
            _stmClient = new Interop.STMCom.TSTMComClass();

            //assinar eventos do componente STM
            _stmClient.OnAcceptStart += new Interop.STMCom.ISTMComEvents_OnAcceptStartEventHandler(_stmClient_OnAcceptStart);
            _stmClient.OnConnect += new Interop.STMCom.ISTMComEvents_OnConnectEventHandler(_stmClient_OnConnect);
            _stmClient.OnDisconnect += new Interop.STMCom.ISTMComEvents_OnDisconnectEventHandler(_stmClient_OnDisconnect);
            _stmClient.OnError += new Interop.STMCom.ISTMComEvents_OnErrorEventHandler(_stmClient_OnError);
            _stmClient.OnReceiveData += new Interop.STMCom.ISTMComEvents_OnReceiveDataEventHandler(_stmClient_OnReceiveData);
            _stmClient.OnStarted += new Interop.STMCom.ISTMComEvents_OnStartedEventHandler(_stmClient_OnStarted);

            //recuperar os parametros de abertura
            StmOpenParameter openParm = parameter as StmOpenParameter;
            if (openParm.UserName == null)
                _stmClient.Cas_Usuario = this.Configuration.UserName;
            else
                _stmClient.Cas_Usuario = openParm.UserName;


            //password sempre deve ser informado pois nao sera armazenado no arquivo de configuracao (muda a cada 30 dias)
            _stmClient.Cas_Senha = openParm.Password;

            if (openParm.Functionary == null)
                _stmClient.Cas_Funcionario = this.Configuration.Functionary;
            else
                _stmClient.Cas_Funcionario = openParm.Functionary;


            //recuperar os objetos utilizados para deserializar as mensagens e armazenar em memória
            _xmlDeserializers = new Dictionary<string, XmlSerializer>();
            _plainTextDeserializers = new Dictionary<string, RecordInfo>();

            //varrer as actions recuperando a forma de deserializar as mensagens
            foreach (StmInputAdapterActionConfiguration actionConfig in this.Configuration.Actions)
            {
                //se foi informado um tipo para deserializacao da mensagem, e o formato da mensagem foi informado
                if (actionConfig.DeserializedMessageType != null)
                {
                    //se for uma mensagem xml
                    if (actionConfig.MessageFormat == StmMessageFormat.Xml)
                    {
                        _xmlDeserializers.Add(actionConfig.Name, new XmlSerializer(actionConfig.DeserializedMessageType));
                    }
                    //se for uma mensagem plainText
                    else if (actionConfig.MessageFormat == StmMessageFormat.PlainText)
                    {
                        _plainTextDeserializers.Add(actionConfig.Name, new RecordInfo(actionConfig.DeserializedMessageType));
                    }
                }
            }
        }



        /// <summary>
        /// Fecha o provider de recebimento de mensagens STM
        /// </summary>
        public override void Close()
        {
            _xmlDeserializers.Clear();
            _plainTextDeserializers.Clear();
            _xmlDeserializers = null;
            _plainTextDeserializers = null;

            base.Close();
        }
    }

}
